package com.adjy.pathbuilders.physics;

import java.util.ArrayList;
import java.util.List;

import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;

import com.adjy.pathbuilders.objects.GameEntityType;
import com.adjy.pathbuilders.rendering.GameCanvas;
import com.adjy.pathbuilders.world.GameEntity;

/**
 * Class that holds properties of a PhysicalWorld
 */
public class PhysicalWorld {

	public final static float GRAVITY = -20.0f;
	private final static Vec2 WORLD_GRAVITY = new Vec2(0.0f, GRAVITY);
	private final static float STEP_RATE = 2f / 60f;

	public final static float SCREEN_RATIO = 1.6f;
	public final static float MINIMUM_POSITION = 0.0f;
	public final static float MAXIMUM_WIDTH = 100;
	public final static float VELOCITY_MULTIPLIER = 0.5f;
	public final static float MAXIMUM_HEIGHT = MAXIMUM_WIDTH / SCREEN_RATIO;

	private World world;
	private List<Body> bodies;
	
	public static final boolean USE_BOX_2D = true;
	

	/**
	 * Creates a PhysicalWorld
	 */
	public PhysicalWorld() {
		// BOX 2D
		world = new World(WORLD_GRAVITY, true);
		bodies = new ArrayList<Body>();
		
		// Step 3: Create Ground Box
		BodyDef groundBodyDef = new BodyDef();
		PolygonShape polygonShape = new PolygonShape();

		// Create Bottom Wall
		groundBodyDef.position.set(new Vec2(PhysicalWorld.MAXIMUM_WIDTH / 2, 0f));
		Body groundBody = world.createBody(groundBodyDef);
		polygonShape.setAsBox(PhysicalWorld.MAXIMUM_WIDTH, 1.0f);
		groundBody.createFixture(polygonShape, 1.0f);
		
		// Creating Right Wall
		groundBodyDef.position.set(new Vec2(PhysicalWorld.MAXIMUM_WIDTH, PhysicalWorld.MAXIMUM_HEIGHT / 2));
		groundBody = world.createBody(groundBodyDef);
		polygonShape.setAsBox(1.0f, PhysicalWorld.MAXIMUM_HEIGHT);
		groundBody.createFixture(polygonShape, 1.0f);
		
		// Creating Top Wall
		groundBodyDef.position.set(new Vec2(PhysicalWorld.MAXIMUM_WIDTH / 2, PhysicalWorld.MAXIMUM_HEIGHT));
		groundBody = world.createBody(groundBodyDef);
		polygonShape.setAsBox(PhysicalWorld.MAXIMUM_WIDTH, 1.0f);
		groundBody.createFixture(polygonShape, 1.0f);
		
		// Creating Left Wall
		groundBodyDef.position.set(new Vec2(0, PhysicalWorld.MAXIMUM_HEIGHT / 2));
		groundBody = world.createBody(groundBodyDef);
		polygonShape.setAsBox(1.0f, PhysicalWorld.MAXIMUM_HEIGHT);
		groundBody.createFixture(polygonShape, 1.0f);
		
		world.setContactListener(new EntityContactListener());
	}
	
	public World getWorld() {
		return this.world;
	}

	/**
	 * Add PhysicalObject to PhysicalWorld
	 * 
	 * @param po
	 *            PhysicalObject to be added
	 */
	public void addObject(GameEntity po) {
		
		if (USE_BOX_2D) {
			// Create Dynamic Body
			BodyDef bodyDef = new BodyDef();
			/*bodyDef.position.set(
					po.getBoundingVolume().getX() + po.getBoundingVolume().getWidth() / 2,
					po.getBoundingVolume().getY() + po.getBoundingVolume().getHeight() / 2);*/
			bodyDef.position.set(
					po.getCenter().x,
					po.getCenter().y);
			
			bodyDef.userData = po;
			
			if (po.isAffectedByGravity()) {
				bodyDef.type = BodyType.DYNAMIC;
			} else {
				bodyDef.type = BodyType.KINEMATIC;
			}
			
			if (po.getType() == GameEntityType.MAINCHAR)
				bodyDef.fixedRotation = true;
			
			Body body = world.createBody(bodyDef);
			bodies.add(body);
			po.setBody(body);
			
			// Assign shape to Body
			FixtureDef fixtureDef = new FixtureDef();
			
			// Shape
			PolygonShape polygonShape = new PolygonShape();
			polygonShape.setAsBox(po.getWidth() / 2, // setAsBox takes half-extends
							po.getHeight() / 2);
			fixtureDef.shape = polygonShape;
			
			fixtureDef.isSensor = !po.isCollidable();
			
			float density = 0.2f;
			float friction = 0.05f;
			float bounce = 0.4f;
			if (po.getType() == GameEntityType.MAINCHAR || po.getType() == GameEntityType.FLOOR)
				bounce = 0.1f;
			
			
			fixtureDef.density = density;
			fixtureDef.friction = friction;
			fixtureDef.restitution = bounce;
			body.createFixture(fixtureDef);
		}
	}

	/**
	 * Removes PhysicalObject from PhysicalWorld
	 * 
	 * @param po
	 *            PhysicalObject to be removed
	 */
	public void removeObject(PhysicalObject po) {
		world.destroyBody(po.getBody());
	}

	/**
	 * Perform all calculations of one frame into the PhysicalWorld
	 * 
	 * @param timePassed
	 *            Elapsed Time in Seconds
	 */
	public void step(float timePassed) {
		
		for (Body b : bodies) {
			PhysicalObject po = (PhysicalObject) b.m_userData;
			b.setLinearVelocity(
					new Vec2(po.getXVelocity() * VELOCITY_MULTIPLIER * b.getMass(),
							b.getLinearVelocity().y));
		}
		
		if (USE_BOX_2D) {
			world.step(STEP_RATE, 8, 4);
		}
		
	}
	
	public void render(GameCanvas canvas) {
		
		for (Body b : bodies) {
			
			canvas.getCanvas().save();
			Vec2 pos = b.getPosition();
			PhysicalObject po = ((PhysicalObject)b.getUserData());
			
			canvas.rotate((float) (180 * b.getAngle() / Math.PI), pos.x, pos.y);
			canvas.drawDebugRect(pos.x - po.getWidth() / 2, pos.y + po.getHeight() / 2,
					po.getWidth(), po.getHeight());
			
			canvas.getCanvas().restore();
			
		}
	}
}